#include "os/os.h"

#include "utility/utility.h"

// Platform specific code for Win32.
#ifndef WIN32_LEAN_AND_MEAN
// WIN32_LEAN_AND_MEAN implies NOCRYPT and NOGDI.
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef NOKERNEL
#define NOKERNEL
#endif
#ifndef NOUSER
#define NOUSER
#endif
#ifndef NOSERVICE
#define NOSERVICE
#endif
#ifndef NOSOUND
#define NOSOUND
#endif
#ifndef NOMCX
#define NOMCX
#endif
// Require Windows 2000 or higher (this is required for the IsDebuggerPresent
// function to be present).
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x500
#endif

#ifdef  _WIN64
#error Windows 64-bit blatforms not supported
#endif

#include <windows.h>

#include <time.h>  // For LocalOffset() implementation.
#include <mmsystem.h>  // For timeGetTime().
#ifdef __MINGW32__
// Require Windows XP or higher when compiling with MinGW. This is for MinGW
// header files to expose getaddrinfo.
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x501
#endif  // __MINGW32__
#ifndef __MINGW32__
#include <dbghelp.h>  // For SymLoadModule64 and al.
#endif  // __MINGW32__
#include <limits.h>  // For INT_MAX and al.
#include <tlhelp32.h>  // For Module32First and al.

// These additional WIN32 includes have to be right here as the #undef's below
// makes it impossible to have them elsewhere.
#include <winsock2.h>
#include <ws2tcpip.h>
#include <process.h>  // for _beginthreadex()
#include <stdlib.h>

#undef VOID
#undef DELETE
#undef IN
#undef THIS
#undef CONST
#undef NAN
#undef GetObject
#undef CreateMutex
#undef CreateSemaphore

namespace l8
{

// Get the system's page size used by VirtualAlloc() or the next power
// of two. The reason for always returning a power of two is that the
// rounding up in OS::Allocate expects that.
static size_t GetPageSize()
{
    static size_t page_size = 0;
    if (page_size == 0)
    {
        SYSTEM_INFO info;
        GetSystemInfo(&info);
        page_size = RoundUpToPowerOf2(info.dwPageSize);
    }

    return page_size;
}

// We keep the lowest and highest addresses mapped as a quick way of
// determining that pointers are outside the heap (used mostly in assertions
// and verification).  The estimate is conservative, ie, not all addresses in
// 'allocated' space are actually allocated to our heap.  The range is
// [lowest, highest), inclusive on the low and and exclusive on the high end.
static void* lowest_ever_allocated = reinterpret_cast<void*>(-1);
static void* highest_ever_allocated = reinterpret_cast<void*>(0);


static void UpdateAllocatedSpaceLimits(void* address, int size)
{
    lowest_ever_allocated = Min(lowest_ever_allocated, address);
    highest_ever_allocated = Max(
            highest_ever_allocated,
            reinterpret_cast<void*>(reinterpret_cast<char*>(address) + size));
}


bool OS::IsOutsideAllocatedSpace(void* pointer)
{
    if (pointer < lowest_ever_allocated || pointer >= highest_ever_allocated)
    {
        return true;
    }

    // Ask the Windows API
    if (IsBadWritePtr(pointer, 1))
    {
        return true;
    }

    return false;
}

// The allocation alignment is the guaranteed alignment for
// VirtualAlloc'ed blocks of memory.
size_t OS::AllocateAlignment()
{
    static size_t allocate_alignment = 0;
    if (allocate_alignment == 0)
    {
        SYSTEM_INFO info;
        GetSystemInfo(&info);
        allocate_alignment = info.dwAllocationGranularity;
    }

    return allocate_alignment;
}


void* OS::Allocate(const size_t requested, size_t* allocated, bool is_executable)
{
    // VirtualAlloc rounds allocated size to page size automatically.
    size_t msize = RoundUp(requested, GetPageSize());

    // Windows XP SP2 allows Data Excution Prevention (DEP).
    int prot = is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
    LPVOID mbase = VirtualAlloc(NULL, msize, MEM_COMMIT | MEM_RESERVE, prot);

    if (mbase == NULL)
    {
        LOG(StringEvent("OS::Allocate", "VirtualAlloc failed"));
        return NULL;
    }

    ASSERT(IsAligned(reinterpret_cast<size_t>(mbase), OS::AllocateAlignment()));

    *allocated = msize;
    UpdateAllocatedSpaceLimits(mbase, msize);
    return mbase;
}

void OS::Free(void* address, const size_t size)
{
    VERIFY(VirtualFree(address, 0, MEM_RELEASE) == TRUE);
    USE(size);
}


int OS::SNPrintF(Vector<char> str, const char* format, ...)
{
      va_list args;
      va_start(args, format);
      int result = VSNPrintF(str, format, args);
      va_end(args);
      return result;
}

int OS::VSNPrintF(Vector<char> str,
                  const char* format,
                  va_list args)
{
#ifdef _MSC_VER
    int n = _vsnprintf_s(str.start(), str.length(), _TRUNCATE, format, args);
    // Make sure to zero-terminate the string if the output was
    // truncated or if there was an error.
    if (n < 0 || n >= str.length())
    {
        str[str.length() - 1] = '\0';
        return -1;
    }
    else
    {
        return n;
    }
#else
    return vsnprintf(str.start(), str.length(), format, args);
#endif
}

void OS::PrintF(const char * format, ...)
{
    va_list args;
    va_start(args, format);

    vfprintf(stdout, format, args);

    va_end(args);
}

} // namespace l8

